<?php

module_load_include("inc", "rdfx", "rdfx.restws.formats");

require_once "functions.inc";

use Guzzle\Service\Client;
use Guzzle\Service\JsonLoader;
use Guzzle\Http\Exception\ClientErrorResponseException;

require_once("Solarium/Autoloader.php");
Solarium_Autoloader::register();

require_once 'ClientConfiguration.php';
require_once 'clients/ImportClient.php';
require_once 'clients/ResourceClient.php';
require_once 'clients/ConfigurationClient.php';
require_once 'clients/SPARQLClient.php';
require_once 'clients/LDPathClient.php';
require_once 'clients/SearchClient.php';
require_once 'clients/CoresClient.php';
require_once 'clients/ReasonerClient.php';
use LMFClient\ClientConfiguration;
use LMFClient\Clients\ConfigurationClient;
use LMFClient\Clients\ImportClient;
use LMFClient\Clients\ResourceClient;
use LMFClient\Clients\LDPathClient;
use LMFClient\Clients\SPARQLClient;
use LMFClient\Clients\SearchClient;
use LMFClient\Clients\CoresClient;
use LMFClient\Clients\ReasonerClient;

class LMF {

    private $uri;

    private $user;

    private $pass;

    function __construct($uri = "http://localhost:8080/LMF", $user = NULL, $pass = NULL) {
        $this->uri = variable_get("lmf_uri", $uri);
        $this->user = variable_get("lmf_user", $user);
        $this->pass = variable_get("lmf_pass", $pass);
    }

    public function getUri() {
        return $this->uri;
    }

    public function getUser() {
        return $this->user;
    }

    public function getPass() {
        return $this->pass;
    }

    public function getConfiguration() {
        return new ClientConfiguration($this->getUri(), $this->getUser(), $this->getPass());
    }

    public function ping() {
        $uri = $this->getUri();
        try {
            $client = new Client($uri);
            $response = $client->head()->send();
            if ($response->getStatusCode() == 200) {
                return TRUE;
            } else {
                return FALSE;
            }
        } catch (Exception $e) {
            return FALSE;
        }
    }

    public function status() {
        if ($this->ping()) {
            global $base_url;
            $status = array();
            $resources = $this->getAllLocalResources();
            foreach($resources as $resource) {
                $item = array();
                $item["id"] = $resource["id"]->getContent();
                $item["uri"] = $resource["s"]->getUri();
                $item["path"] = str_replace($base_url, "", $item["uri"]);
                $item["raw"] = $this->getResourceUrl($item["uri"]);
                $item["date-lmf"] = $this->getLastModifiedDate($item["uri"]);
                $alias = drupal_lookup_path("alias", "node/" . (int)$item["id"]);
                $path = (empty($alias) ? "/node/" . $item["id"] : "/" . $alias);
                if (strcmp($item["path"], $path) == 0) {
                    $node = node_load((int)$item["id"]);
                    $item["date-drupal"] = $node->changed;
                } else {
                     $item["date-drupal"] = NULL;
                }
                $status[] = $item;
            }
            return $status; 
        } else {
            drupal_set_message("Unable to access LMF at <a href=\"" . $this->getUri() . "\">" . $this->getUri() . "</a>", "error");
            return array();
        }
    }

    public function setConfiguration($key, $value) {
        $config = $this->getConfiguration();
        $cfg = new ConfigurationClient($config);
        $cfg->setConfiguration("solr.local_only","false");
        return true;
    }

    public function createSearchCore($core, $configuration) {
        $config = $this->getConfiguration();
        $cores = new CoresClient($config);
        $cores->createCoreConfiguration($core, $configuration);
        return true;
    }

    public function deleteSearchCore($core) {
        $config = $this->getConfiguration();
        $cores = new CoresClient($config);
        $cores->deleteCore($core);
    }

    public function addReasoningProgram($name, $program) {
        $config = $this->getConfiguration();
        $skwrl = new ReasonerClient($config);
        $skwrl->uploadProgram($name, $program);
        return true;
    }

    private function importRdf($data, $content_type="application/rdf+xml") {
        try {
            $config = $this->getConfiguration();      
            $import = new ImportClient($config);
            $import->uploadDataset($data, $content_type);
        } catch (Exception $e) {
            return false;
        }
    }

    private function getAllLocalResources() {
        global $base_url;
        $query =
            "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n" .
            "PREFIX dct: <http://purl.org/dc/terms/> \n" .
            "SELECT DISTINCT ?s ?id \n".
            "WHERE { \n" .
            "  ?s rdf:type ?o . \n" .
            "  ?s dct:identifier ?id . \n" .
            "  FILTER(STRSTARTS(STR(?s), '" . $base_url . "')) \n" .
            "} \n" .
            "ORDER BY ?s \n";

        //TODO: fixing at the client side a bug with distinct in the sparql engine
        $resources = array();
        $uris = array();
        foreach($this->execSparql($query) as $resource) {
            $uri = $resource["s"]->getUri();
            if (!in_array($uri, $uris)) {
                $uris[] = $uri;
                $resources[] = $resource;
            }
        } 
        return $resources;
    }

    private function getLastModifiedDate($uri) {
        $client = new ResourceClient($this->getConfiguration());
        $url = $client->getServiceUrl($uri);
        try {
            $result = drupal_http_request($url, array("method"=> "HEAD", "headers" => array("Accept" => "application/rdf+xml")));
            if ($result->code < 400) {
                return strtotime($result->headers["last-modified"]);
            } else {
                return NULL;
            }
        } catch(Exception $e) {
            return NULL;
        }
    }

    public function existsResource($uri) {
        $config = $this->getConfiguration();
        try {
            $client = new ResourceClient($config);
            return $client->existsResource($uri);
        } catch (Exception $e) {
            $msg = "Can't connect with LMF at ". $config->getBaseUrl() . " while cheching existence of " . $uri; //. ": " . $e->getMessage(); 
            dfb($msg, $label=NULL);
            drupal_set_message($msg, "error");
            return false;
        }
    }

    public function deleteResource($uri, $actual=true) {
        $config = $this->getConfiguration();
        $resources = new ResourceClient($config);
        if ($resources->existsResource($uri)) {
            if ($actual) {
                $resources->deleteResource($uri);
            } else {
                $this->execSparqlUpdate("DELETE { <" . $uri . "> ?p ?o } WHERE { <" . $uri . "> ?p ?o } ");
            }
            return true;
        }  else {
            return false;
        }   
    }

    public function getResourceUrl($uri) {
        $resourceClient = new ResourceClient($this->getConfiguration());
        return $resourceClient->getServiceUrl($uri);
    }

    public function getPrefixes() {
        if (lmf_ping()) {
            //TODO: move this feature to the client library
            $config = $this->getConfiguration();
            $uri = $this->getUri() . "/prefix";
            try {
                $client = new Client();
                $request = $client->get($this->getServiceUrl($uri),array(
                    "User-Agent"   => "LMF Client Library (PHP)",
                    "Accept" => "application/json"
                ));
                // set authentication if given in configuration
                if(!_isset($config->getUsername())) {
                    $request->setAuth($config->getUsername(),$config->getPassword());
                }
                $response = $request->send();

                if($response->getStatusCode() == 200) {
                    $loader = new JsonLoader();
                    return $loader->parseJsonFile($response->getBody(true));
                } else {
                    return array();
                }
            } catch(Exception $ex) {
                return array();
            }
        } else {
            return array();
        }
    }

    public function getNodeUri($nid) {
        //return $this->getNodeAliasUri($nid); //FIXME: restws module does not works fine with aliases
        return $this->getNodeBasicUri($nid);
    }

    private function getNodeBasicUri($nid) {
        global $base_url;
        return $base_url . "/node/" . $nid;
    }

    private function getNodeAliasUri($nid) {
        global $base_url;
        return $base_url . "/" . drupal_get_path_alias("node/" . $nid);
    }

    public function importNode($nid) {
        $node = node_load(array("nid" => ((int)$nid)));
        $uri = $this->getNodeUri($nid);
        $url = $this->getResourceUrl($uri);
        if ($node != false) {
            try {
                if($this->importNodeId($nid)) {
                    return "<a href=\"" . $uri . "\">Node " . $nid . "</a> successfully <a href=\"" . $url . "\">imported into LMF</a>.</div>";
                } else {
                    return "<div class=\"messages error\">Unable to export <a href=\"" . $uri . "\">node " . $nid . "</a> as RDF/XML from <a href=\"" . $uri . ".rdf\">" . $uri . ".rdf</a> (HTTP Status Code " . $response->code . ")</div>";
                }
            } catch(Exception $e) {
                return "<div class=\"messages error\">Unable to import the <a href=\"" . $uri . "\">node " . $nid . "</a> into LMF: " . $e->getMessage() . "</div>";
            }
        } else {
            return "<div class=\"messages error\">Node " . $nid . " not found in the current Drupal installation, <a href=\"import?\">try again</a>.</div>";
        } 
    }

    private function importNodeId($nid) {
        $rdf = $this->getNodeRdf($nid);
        if ($rdf != NULL) {
            $uri = $this->getNodeBasicUri($nid);
            $config = $this->getConfiguration();
            $this->deleteResource($uri);       
            $import = new ImportClient($config);
            $import->uploadDataset($rdf, "application/rdf+xml");

            //extra triples
            $extra_triples = "";
            $alias_uri = $this->getNodeAliasUri($nid);
            if (strcmp($uri, $alias_uri) != 0) {
                $extra_triples .= "<" . $alias_uri . "> <http://purl.org/dc/terms/identifier> \"" . $nid . "\"^^<http://www.w3.org/2001/XMLSchema#integer> . \n";
                $extra_triples .= "<" . $alias_uri . "> <http://xmlns.com/foaf/0.1/page> <" . $uri . "> . \n";
                $extra_triples .= "<" . $uri . "> <http://xmlns.com/foaf/0.1/primaryTopic> <" . $alias_uri . "> . \n";
                $extra_triples .= "<" . $alias_uri . "> <http://www.w3.org/2000/01/rdf-schema#seeAlso> <" . $this->getResourceUrl($alias_uri) . "> . \n";
            } else { 
                $extra_triples .= "<" . $uri . "> <http://purl.org/dc/terms/identifier> \"" . $nid . "\"^^<http://www.w3.org/2001/XMLSchema#integer> . \n";
                $extra_triples .= "<" . $uri . "> <http://www.w3.org/2000/01/rdf-schema#seeAlso> <" . $this->getResourceUrl($uri) . "> . \n";
            }
            $import->uploadDataset($extra_triples, "text/plain");

            return true;
        } else {
            drupal_set_message("Node " . $nid . " could not be retrieved from restws (HTTP Status Code " . $response->code . ")", "error");
            return false;
        }
    }

    private function getNodeRdf($nid) {
        return $this->getNodeRdfHttp($nid);
        //return $this->getNodeRdfNatively($nid)
    }

    private function getNodeRdfHttp($nid) {
        $uri = $this->getNodeUri($nid);
        $url = $uri . ".rdf";
        //TODO: http://stackoverflow.com/questions/7149856/using-current-session-for-drupal-http-request
        $result = drupal_http_request($url, array("method"=> "GET", "headers" => array("Accept" => "application/rdf+xml")));
        if ($result->code == 200) {
            return $result->data;
        } else {
            return NULL;
        }
    }

    private function getNodeRdfNatively($nid) {
        $formatter = new RDFxRestWSFormatRDFXML("LmfRdfXml", "Custom formatter for getting nodes as RDF for LMF");
        return $formatter->viewResource("FIXME", $nid);
    }

    public function importType($type) {
        $nodes = listNodes($type);
        $count = count($nodes);
        $imported = 0;
        foreach($nodes as $node) {
            try {
                if ($this->importNodeId($node->nid)) {
                    $imported++;
                }
            } catch(Exception $e) {
                drupal_set_message("Error importing node ". $node->nid . ": " . $e->getMessage(), "error");
            }
        }
        return $imported . "/" . $count . " nodes with the content type <tt>" . $type . "</tt> have been imported into LMF.";
    }

    public function importTaxonomy($vid) {
        if (strcmp("*", $vid) == 0) {
            $vocabs = 0;
            $terms = 0;
            foreach(taxonomy_get_vocabularies() as $vocabulary) {
                $n = $this->importTaxonomyById($vocabulary->vid);
                if ($n != -1) {
                    $vocabs++;
                    $terms += $n;
                }
            }
            return $vocabs . " taxonomy vocabularies (" . $terms . " terms in total) have been successfully imported into LMF.";
        } else {
            $n = $this->importTaxonomyById($vid);
            if ($n != -1) {
                return $n . " terms of the taxonomy '" . $vid . "' have been successfully imported into LMF.";
            } else {
                return "<div class=\"messages error\">Taxonomy " . $vid . " not found or empty in the current Drupal installation, <a href=\"import?\">try again</a>.</div>.";
            }
        } 
    }

    private function importTaxonomyById($vid) {
        $vocab = taxonomy_vocabulary_load($vid);
        if ($vocab != NULL) {
            global $base_url;
            $uri = $base_url . "/taxonomy/vocabulary/" . $vocab->machine_name;
            
            $terms = taxonomy_get_tree($vid);
            $members = array();
            foreach ($terms as $term) {
                try {
                    if ($this->importTaxonomyTerm($term->tid)) {
                        $members[] = $this->getTaxonomyTermUri($term->tid);
                    }
                } catch(Exception $e) {
                    drupal_set_message("Error importing taxonomy term ". $term->name . "(" . $term->tid . "): " . $e->getMessage(), "error");
                }
            }

            $triples = "<" . $uri . "> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://www.w3.org/2004/02/skos/core#Collection> . \n";
            $triples .= "<" . $uri . "> <http://purl.org/dc/terms/identifier> \"" . $vid . "\"^^<http://www.w3.org/2001/XMLSchema#integer> . \n";
            foreach ($members as $member) {
                $triples .= "<" . $uri . "> <http://www.w3.org/2004/02/skos/core#member> <" . $member . "> . \n";
            }
            $this->importRdf($triples, "text/plain");
            return count($members);
        } else {
            return -1;
        }
    }

    private function importTaxonomyTerm($tid) {
        $rdf = $this->getTaxonomyTermRdf($tid);
        if ($rdf != NULL) {
             global $base_url;

            //FIXME: inconsistency with the uris generated by drupal for taxonomies terms
            //       further details at http://drupal.org/node/1846646
            $uri = $this->getTaxonomyTermUri($tid);
            $wrong_uri = $base_url . "/taxonomy_term/" . $tid;
            $rdf = str_replace($wrong_uri, $uri, $rdf);

            $config = $this->getConfiguration();
            $this->deleteResource($uri);       
            $import = new ImportClient($config);
            $import->uploadDataset($rdf, "application/rdf+xml");

            //extra triples
            $extra_triples = "";
            $extra_triples .= "<" . $uri . "> <http://purl.org/dc/terms/identifier> \"" . $tid . "\"^^<http://www.w3.org/2001/XMLSchema#integer> . \n";
            $import->uploadDataset($extra_triples, "text/plain");

            return true;
        } else {
            drupal_set_message("Taxonomy term " . $tid . " could not be retrieved from restws (HTTP Status Code " . $response->code . ")", "error");
            return false;
        }
    }

    private function getTaxonomyTermUri($tid) {
        global $base_url;
        //$uri = $base_url . "/" . drupal_get_path_alias(taxonomy_term_path($term) );
        $entity_uri = entity_uri("taxonomy_term", taxonomy_term_load($tid));
        return $base_url . "/" . drupal_get_path_alias($entity_uri["path"]);
    }

    private function getTaxonomyTermRdf($tid) {
        global $base_url;
        $url = $base_url . "/taxonomy_term/" . $tid . ".rdf";
        //TODO: http://stackoverflow.com/questions/7149856/using-current-session-for-drupal-http-request
        $result = drupal_http_request($url, array("method"=> "GET", "headers" => array("Accept" => "application/rdf+xml")));
        if ($result->code == 200) {
            return $result->data;
        } else {
            return NULL;
        }
    }

    public function importResource($uri) {
        $config = $this->getConfiguration();

        $resources = new ResourceClient($config);

        if (!$resources->existsResource($uri)) {
            $resources->createResource($uri);
        }

        /*
        if ($resources->existsResource($uri)) {
            //force caching
            $ldpath_program =
                "@prefix mao : <http://www.w3.org/ns/ma-ont#>; " .
                "title = mao:title :: xsd:string; " .
                "description = mao:description :: xsd:string; " .
                "keywords = mao:hasKeyword :: xsd:string; ";
            $ldpath = new LDPathClient($config);
            $props = $ldpath->evaluateProgram($uri, $ldpath_program);
            return true;
        } else {
            return false;
        }
        */
        return true;
    }

    public function execSparql($query) {
        try {
            $config = $this->getConfiguration();
            $sparql = new SPARQLClient($config);
            return $sparql->select($query);
        } catch (Exception $e) {
            return array();
        }
    }

    public function execSparqlUpdate($query) {
        try {
            $config = $this->getConfiguration();
            $sparql = new SPARQLClient($config);
            $sparql->update($query);
            return TRUE;
        } catch (Exception $e) {
            return FALSE;
        }
    }

    public function execLDPath($program) {
        return NULL; //TODO
    }

    public function search($query, $core) { 
        $config = $this->getConfiguration();
        $search = new SearchClient($config);
        $q = "title:(" . $query . ")^2 ".
             "summary:(" . $query . ") ".
             "tag:(" . $query . ")^4";
        return $search->simpleSearch($core, $q);
    }

    public function getRecommendation($query, $core) { 
        $config = $this->getConfiguration();
        $search = new SearchClient($config);
        return $search->recommendations($core, $query, array("title" => 2, "summary" => 1, "tag" => 4));
    }

    public static function getStaticPage($name) {
        return read_file(dirname(__FILE__) . "/html/" . $name . ".html");
    }

}

?>
